﻿module StochasticSimulation.Simulator

open System
open System.Diagnostics
open StochasticSimulation.ChemicalReaction.ChemicalSpecies
open StochasticSimulation.ChemicalReaction.ChemicalReaction
open System.Collections.Generic
open StochasticSimulation.GillespieAlgorithm
open StochasticSimulation.ChemicalReaction.ReactionGraph
open StochasticSimulation.ChemicalReaction.PriorityQueue


type ProgressArgs (t, args:SpeciesCount list) =
    inherit EventArgs()

    member this.Latest = args

    member this.Time = t

type ProgressUpdateDelegate = delegate of obj * ProgressArgs -> unit



type Simulator(system, rs) =

    // todo : pass in from client
    let initialSystem = system
    
    // todo : read this from a parser
    let reactions = rs

    let mutable graph = new Graph( Array.toList reactions)
    let q = new PriorityQueue( initialSystem, (Array.toList reactions) )
    
    let progressEvent = new Event<ProgressUpdateDelegate, ProgressArgs>()
    let PROGRESS_THRESHOLD = 0.25

    member this.getAlgorithm ( name ) =
        match name with
        | "Gillespie" -> StochasticSimulation.GillespieAlgorithm.executeNextReaction
        | "GillespieFirst" -> StochasticSimulation.GillespieFirstAlgorithm.executeNextReaction
        | "GibsonBruck" -> StochasticSimulation.GibsonBruckAlgorithm.executeNextReaction graph q
        | _ -> raise (NotImplementedException(name))
        

    [<CLIEvent>]
    member this.OnProgress = progressEvent.Publish
                
    member this.ExecuteReaction reactionStrategy seed timeLimit =
        let writeStatus (t:double) last system =
            if t - last > PROGRESS_THRESHOLD then 
                progressEvent.Trigger(this, new ProgressArgs(t, system) )
                t
            else 
                last
    
        let rng = new Random(seed)
        let bumpTriggerTime = 0.5
        let rec executeReaction strategy t system toBump lastWrite =
            let r = strategy reactions t rng system   

            match (t, toBump) with
            | (_, _) when t > timeLimit -> (t,system)
            | (_, true) when t > bumpTriggerTime   -> 
                //TODO : turn to DI function and pass in from client
                reactions.[4] <- { Rate = 5000.0 ; Changes = dict[ {Name = "S"}, {Destroyed =0 ; Created=1}] };
                graph <- new Graph(Array.toList reactions)
                executeReaction reactionStrategy r.tau (Seq.toList r.UpdatedSystem) false (writeStatus t lastWrite system)
            | (_,_)                     ->
                executeReaction strategy r.tau (Seq.toList r.UpdatedSystem) (t < bumpTriggerTime) (writeStatus t lastWrite system)
        let (time,finalSystem) = executeReaction reactionStrategy 0.0 (Seq.toList initialSystem) true 0.0
        finalSystem
    